{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# `Nuqleon.Json`\n",
        "\n",
        "Provides a simple object model for JSON, a parser, a printer, and visitors.\n",
        "\n",
        "> **Note:** The history of this assembly goes all the way back 2009, before `Newtonsoft.Json` became the de facto standard for JSON serialization on .NET. It also predates the `System.Text.Json` library in the BCL."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Reference the library"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 1 - Use a local build\n",
        "\n",
        "If you have built the library locally, run the following cell to load the latest build."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"bin/Debug/net6.0/Nuqleon.Json.dll\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 2 - Use NuGet packages\n",
        "\n",
        "If you want to use the latest published package from NuGet, run the following cell."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"nuget:Nuqleon.Json,*-*\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## (Optional) Attach a debugger\n",
        "\n",
        "If you'd like to step through the source code of the library while running samples, run the following cell, and follow instructions to start a debugger (e.g. Visual Studio). Navigate to the source code of the library to set breakpoints."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "System.Diagnostics.Debugger.Launch();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## The JSON object model\n",
        "\n",
        "This library provides a straightforward object model for JSON consisting of `null`, Boolean values, Number values, String values, arrays, and objects. An example of constructing a JSON object using factory methods is shown below."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "using Nuqleon.Json.Expressions;\n",
        "using static Nuqleon.Json.Expressions.Expression;\n",
        "\n",
        "var json =\n",
        "    Object(new Dictionary<string, Expression>\n",
        "    {\n",
        "        {\n",
        "            \"Name\",\n",
        "            String(\"Bart\")\n",
        "        },\n",
        "        {\n",
        "            \"Age\",\n",
        "            Number(\"21\")\n",
        "        },\n",
        "        {\n",
        "            \"Hobbies\",\n",
        "            Array(\n",
        "                String(\"Code\"),\n",
        "                String(\"Walk\"),\n",
        "                String(\"Run\")\n",
        "            )\n",
        "        }\n",
        "    });"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "JSON objects can be printed to produce a JSON string."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "{\"Name\":\"Bart\",\"Age\":21,\"Hobbies\":[\"Code\",\"Walk\",\"Run\"]}\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "string s = json.ToString();\n",
        "\n",
        "Console.WriteLine(s);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Alternatively, the `ToString` method has an overload that accepts a `StringBuilder` to append the JSON text to.\n",
        "\n",
        "The default printing isn't pretty; it's compact. Using the `PrettyPrinter` we can create a more friendly representation."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "{\r\n  \"Name\": \"Bart\",\r\n  \"Age\": 21,\r\n  \"Hobbies\": [\r\n    \"Code\",\r\n    \"Walk\",\r\n    \"Run\"\r\n  ]\r\n}\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "var printer = new PrettyPrinter(indentationWidth: 2, indentationCharacter: ' ');\n",
        "\n",
        "string pretty = printer.Visit(json);\n",
        "\n",
        "Console.WriteLine(pretty);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "It may come as a surprise to see that the pretty printer does not have a `Print` method; instead, we use a method called `Visit`. This is because the pretty printer is implemented as a visitor over the JSON object. We can write custom visitors as well."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "Bart, Code, Walk, Run\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "class FindStringConstants : ExpressionVisitor\n",
        "{\n",
        "    public List<string> Strings { get; } = new List<string>();\n",
        "\n",
        "    public override Expression VisitConstant(ConstantExpression c)\n",
        "    {\n",
        "        if (c.NodeType == ExpressionType.String)\n",
        "        {\n",
        "            Strings.Add((string)c.Value);\n",
        "        }\n",
        "\n",
        "        return c;\n",
        "    }\n",
        "}\n",
        "\n",
        "var fsc = new FindStringConstants();\n",
        "\n",
        "fsc.Visit(json);\n",
        "\n",
        "Console.WriteLine(string.Join(\", \", fsc.Strings));"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The `PrettyPrinter` is implemented as an `ExpressionVisitor<string>` where each node is transformed into a string."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Parsing JSON\n",
        "\n",
        "Existing JSON fragments can also be parsed into the JSON object model representation using `Expression.Parse`. An example is shown below."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "string json = \"{\\\"bar\\\": 42, \\\"foo\\\": false, \\\"qux\\\": [ null, 3.14 ]}\";\n",
        "\n",
        "Expression expr = Expression.Parse(json);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "To illustrate the different node types and their properties, we'll assert the structure of the resulting JSON object using a bunch of type checks. Note that Number values are represented with a `string`-based `Value` on `ConstantExpression`. This is done to ensure a full-fidelity representation of the nunmber rather than causing a conversion to any of the .NET types, which may be lossy."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "True\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "bool test =\n",
        "    expr is ObjectExpression o && o.Members.Count == 3 &&\n",
        "        o.Members[\"bar\"] is ConstantExpression bar && bar.NodeType == ExpressionType.Number &&\n",
        "            bar.Value.Equals(\"42\") &&\n",
        "        o.Members[\"foo\"] is ConstantExpression foo && foo.NodeType == ExpressionType.Boolean &&\n",
        "            foo.Value.Equals(false) &&\n",
        "        o.Members[\"qux\"] is ArrayExpression qux && qux.ElementCount == 2 &&\n",
        "            qux.Elements[0] is ConstantExpression qux0 && qux0.NodeType == ExpressionType.Null &&\n",
        "            qux.Elements[1] is ConstantExpression qux1 && qux1.NodeType == ExpressionType.Number &&\n",
        "                qux1.Value.Equals(\"3.14\");\n",
        "\n",
        "Console.WriteLine(test);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## JSON serialization\n",
        "\n",
        "This library also contains a rudimentary JSON serializer and deserializer that goes back all the way to 2009. It is kept for compatibility but alternatives are strongly recommended for newer code. An example of its usage is shown below."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "Person { Name = Bart, Age = 21 }\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "{\"Name\":\"Bart\",\"Age\":21}\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "Person { Name = Bart, Age = 21 }\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "True\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "using Nuqleon.Json.Serialization;\n",
        "\n",
        "record Person\n",
        "{\n",
        "    public string Name { get; init; }\n",
        "    public int Age { get; init; }\n",
        "}\n",
        "\n",
        "var ser = new JsonSerializer(typeof(Person));\n",
        "\n",
        "var oldBart = new Person { Name = \"Bart\", Age = 21 };\n",
        "\n",
        "Console.WriteLine(oldBart);\n",
        "\n",
        "string json = ser.Serialize(oldBart);\n",
        "\n",
        "Console.WriteLine(json);\n",
        "\n",
        "var newBart = (Person)ser.Deserialize(json);\n",
        "\n",
        "Console.WriteLine(newBart);\n",
        "\n",
        "Console.WriteLine(oldBart == newBart);"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": ".NET (C#)",
      "language": "C#",
      "name": ".net-csharp"
    },
    "language_info": {
      "file_extension": ".cs",
      "mimetype": "text/x-csharp",
      "name": "C#",
      "pygments_lexer": "csharp",
      "version": "9.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}